Fix: chat/prompt not persisting scene changes — throw on missing active scene - #561
Fix: chat/prompt not persisting scene changes — throw on missing active scene#561Srujanreddy1234 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 23a2f04. Configure here.
| expect(m.content.text).toContain('load_scene') | ||
| expect(m.content.text).toContain('create_project') | ||
| expect(m.content.text).toContain('create_house_from_brief') | ||
| expect(m.content.text).toContain('no bound scene') |
There was a problem hiding this comment.
Prompt test substring mismatch
High Severity
The new binding-scene test expects the prompt to contain "no bound scene", but the actual prompt text uses "Without a bound scene". This string mismatch causes the test assertion to fail.
Reviewed by Cursor Bugbot for commit 23a2f04. Configure here.
| expect(events.length).toBe(1) | ||
| expect(events[0]!.kind).toBe('create_wall') | ||
| expect(events[0]!.version).toBe(2) | ||
| }) |
There was a problem hiding this comment.
Tests need missing store events
High Severity
New regression tests call store.listSceneEvents and expect publishLiveSceneSnapshot to save and emit events when using InMemorySceneStore, but that test store does not implement appendSceneEvent or listSceneEvents, so canAppendSceneEvents is false and publish returns without persisting.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 23a2f04. Configure here.
Aymericr
left a comment
There was a problem hiding this comment.
Thanks for digging into this — the observation at the heart of the PR is correct and worth writing down: publishLiveSceneSnapshot swallowing mutations on an unbound session gives the caller zero signal, and the from_brief tool description ("Produces a plan of apply_patch calls", packages/mcp/src/prompts/from-brief.ts:56) was genuinely stale and misleading. That description fix I'd take as-is.
I can't merge the rest as it stands. I built a scratch clone of the PR ref and ran it; two things block it, and the tests are red.
1. This can't be the cause of #557. The chat on editor.pascal.app is served by our hosted app, not packages/mcp. Its scene tools are schema-only on the server and are executed in the browser — create_room/add_door/add_window/furnish_room are dispatched from the hosted tool executor straight into the Zustand store, and persistence runs through project autosave. There is no call into publishLiveSceneSnapshot anywhere in that path (grep across the hosted AI lib returns 0 hits; the only @pascal-app/mcp import is a type-only ActiveSceneMeta). So this change, whatever else it does, does not move #557 — please drop that framing from the title/body.
2. It breaks the documented CLI flow. bin/pascal-mcp.ts:64 always attaches a SQLite store, and nothing in the bin ever binds an active scene (:59-61 only loadJSON/loadDefault). So hasStore is true for every pascal-mcp --stdio session, and with this patch the first mutation hard-fails: create_wall → MCP error -32600: no_active_scene. That's the README quick start (packages/mcp/README.md:24-34). I also confirmed the build-then-save flow becomes impossible: create_wall errors while save_scene still succeeds, so you can't build in memory and save at the end. This is the one blocker I'd want solved before merge, and gating on something narrower than hasStore — or lazily creating and binding a draft scene on first mutation — is a much better shape than erroring.
The tests in the PR don't pass. On 23a2f045, bun run test in packages/mcp is 301 pass / 4 fail; main is 297/297 green, and merging onto current main is conflict-free with tsc --build exiting 0 — so these are the PR's failures, not a stale base:
prompts.test.ts:92asserts'no bound scene'; the prompt says'Without a bound scene'(from-brief.ts:11) — Bugbot flagged this and it's still unfixed on head.live-sync.test.ts:62and:94callstore.listSceneEvents, butInMemorySceneStore(tools/scene-lifecycle/test-utils.ts) implements neitherappendSceneEventnorlistSceneEvents, socanAppendSceneEventsis false and publish early-returns — also flagged and unfixed.prompts.test.ts:341usesasset.src: '', which fails theAssetUrlallowlist with a ZodError. This is the flagship "full bedroom regression" test the PR body cites as verification; not one of its assertions has ever run.
And bun run check fails with 3 biome errors, all in these two files (bunx biome check --write packages/mcp fixes them) — that gates both ci.yml and mcp-ci.yml.
One thing I want to explicitly not hold against this PR. My first read flagged that the new throw fires after the caller has already mutated the bridge (create-wall.ts:65 createNode, then :66 publish), so a failed tool call strands nodes that a later save_scene persists. That's real, but I checked it against main before asking you to fix it, and it's pre-existing: main already throws post-mutation from the same function at live-sync.ts:58 (live_sync_version_conflict) and :64 (live_sync_failed), and I reproduced identical accumulation there with no patch involved. On your exact repro — 3 × create_room then save_scene — main and this branch leave a byte-for-byte identical graph ({zone:3, slab:3, ceiling:3, wall:12}) and persist the same 3 duplicate zones; the only delta is that main reports success while doing it. So this PR doesn't make anything worse there, and the recovery your error message names actually clears the orphans (load_scene → bridge.loadJSON replaces the whole graph; verified it collapses back to {site:1, building:1, level:1}). Transactional mutation semantics are worth a separate issue, not a condition on your diff.
Two smaller things: please delete test-flow.mjs — it sits outside biome's globs, imports src/*.js paths that don't exist, and bun test-flow.mjs dies on Cannot find module '@modelcontextprotocol/sdk/client/index.js' since the root has no MCP SDK dep; that coverage belongs in room-tools.test.ts. And the preamble rewrite quietly drops the "first create/load a Site and Building" guidance — please restore it or call the removal out. (Relatedly, apply_patch is still in the prompt, so deleting the assertion at prompts.test.ts:64 wasn't needed.)
Happy to keep going if you want to re-scope this to "MCP unbound-session mutations are silently dropped" as package hardening — keep the CLI flow working, fix the four tests, drop the #557 framing. The tool-description one-liner I'd merge today as its own PR.


Problem
The chat/prompt interface in the Pascal 3D floor-plan editor wasn't building or editing anything based on user prompts. When a user submitted a prompt like "Add a bedroom to the current floor plan...", the AI agent's tool calls (create_room, add_door, add_window, furnish_room) executed and mutated the in-memory Zustand store, but no changes were persisted to SQLite and no SSE events were emitted — so the browser never received the updates. From the user's perspective, the assistant appeared to respond, but nothing was ever built.
Root Cause
publishLiveSceneSnapshot() in live-sync.ts was silently returning when no active scene was bound to the MCP session:
const active = operations.getActiveScene()
if (!(active && operations.canAppendSceneEvents)) return // silent no-op
This meant any mutation made without an actively bound scene was dropped with no error, no log, and no signal to the caller.
Compounding this, the from_brief MCP prompt never instructed the LLM to call load_scene / create_project / create_house_from_brief before invoking mutation tools. As a result, the LLM would frequently jump straight to create_room etc. on a fresh session — which then silently failed to persist due to the issue above.
Fix
Tests
How to verify
Note
Medium Risk
Changes core MCP persistence behavior for all mutation paths that call live sync; mis-bound sessions now error instead of silently dropping work, which is intentional but affects agent UX.
Overview
Fixes chat/prompt edits that appeared to succeed but never showed in the editor by making live sync fail loudly when a
SceneStoreis attached but no scene is bound, instead of silently skipping persistence and SSE events.publishLiveSceneSnapshotnow throwsno_active_scenewith guidance to callload_scene,create_project, orcreate_house_from_briefbefore mutation tools; headless mode (no store) still no-ops unchanged.The
from_briefMCP prompt adds a CRITICAL FIRST STEP to bind a scene first, restructures the task into bind → build → validate steps, and updates the prompt description away from implyingapply_patch-only workflows.Tests cover live-sync throw/success/versioning, prompt text for scene binding, and a bedroom-style regression that mutations persist and emit the expected scene events when an active scene is set. A root
test-flow.mjsscript exercises room tools over in-memory MCP transport.Reviewed by Cursor Bugbot for commit 23a2f04. Bugbot is set up for automated code reviews on this repo. Configure here.